﻿using System;
using System.IO;
using FirstFloor.Documents.Controls.Commands;
using FirstFloor.Documents.IO;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Browser;
using System.Net;
using FirstFloor.Documents.IO.DotNetZip;

namespace SampleBrowser.Commands
{
    /// <summary>
    /// The command that opens a document, sent it to the server for conversion and assigns the XPS result to a DocumentDataSource.
    /// </summary>
    public class OpenAndConvertRemoteCommand
        : OpenFromDiskCommand
    {
        /// <summary>
        /// Encapsulates the information of a single upload request.
        /// </summary>
        class UploadRequest
        {
            public Stream FileStream { get; set; }
            public HttpWebRequest Request { get; set; }
        }

        private bool isConverting = false;

        /// <summary>
        /// Initializes a new instance of the <see cref="OpenAndConvertRemoteCommand"/> class.
        /// </summary>
        public OpenAndConvertRemoteCommand()
        {
            this.Filter = "All Documents (*.doc,*.docx,*.xml,*.odt,*.htm,*.html,*.rtf)|*.doc;*.docx;*.xml;*.odt;*.htm;*.html;*.rtf|Word Documents (*.doc,*.docx)|*.doc;*.docx|WordML Documents (*.xml)|*.xml|OpenDocument Text (*.odt)|*.odt|HTML Pages (*.htm,*.html)|*.htm;*.html|RTF Documents (*.rtf)|*.rtf|All files (*.*)|*.*";
        }

        /// <summary>
        /// Defines the method that determines whether the command can execute in its current state.
        /// </summary>
        /// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
        /// <returns>
        /// true if this command can be executed; otherwise, false.
        /// </returns>
        public override bool CanExecute(object parameter)
        {
            return base.CanExecute(parameter) && !this.isConverting;
        }

        /// <summary>
        /// Implements the actual command execution.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        protected override void OnExecute(object parameter)
        {
            var dlg = new OpenFileDialog();
            dlg.Multiselect = false;
            dlg.Filter = this.Filter;

            if (true == dlg.ShowDialog()) {
                if (dlg.File.Length > 2 << 18) {
                    MessageBox.Show("The selected document is too large. This demo limits the file size to 512KB.\n\nPlease select a smaller document.", "Document too large", MessageBoxButton.OK);
                    return;
                }

                // upload the file bytes for conversion
                UploadDocument(dlg.File);
            }
        }

        /// <summary>
        /// Creates a package reader for given stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns></returns>
        public override IPackageReader CreatePackageReader(Stream stream)
        {
            return new DotNetZipPackageReader(stream);
        }

        private void UpdateState(bool isConverting)
        {
            if (this.isConverting != isConverting) {
                this.isConverting = isConverting;
                OnCanExecuteChanged();
            }
        }

        private void UploadDocument(FileInfo file)
        {
            UpdateState(true);

            // properly dispose of the current loaded package reader
            this.DocumentDataSource.PackageReader = null;

            try {
                // create an upload request
                var uri = new Uri(HtmlPage.Document.DocumentUri, "DocumentService.ashx?convert");
                var request = (HttpWebRequest)WebRequest.Create(uri);
                var uploadRequest = new UploadRequest() {
                    FileStream = file.OpenRead(),
                    Request = request
                };
                request.Method = "POST";

                // async get request stream
                request.BeginGetRequestStream(OnGetRequestStream, uploadRequest);
            }
            catch (Exception ex) {
                UpdateState(false);

                MessageBox.Show(ex.ToString());
            }
        }

        private void OnGetRequestStream(IAsyncResult result)
        {
            var uploadRequest = (UploadRequest)result.AsyncState;
            try {
                // upload bytes to the server
                using (var stream = uploadRequest.Request.EndGetRequestStream(result)) {
                    var buffer = new byte[4096];
                    int bytesRead;

                    while ((bytesRead = uploadRequest.FileStream.Read(buffer, 0, buffer.Length)) != 0) {
                        stream.Write(buffer, 0, bytesRead);
                    }
                }

                // async get response
                uploadRequest.Request.BeginGetResponse(OnGetResponse, uploadRequest);
            }
            catch (Exception e) {
                Dispatcher.BeginInvoke(() => {
                    UpdateState(false);
                    MessageBox.Show(e.ToString());
                });
            }
            finally {
                // ensure file is properly closed after sending either succeeded or failed
                uploadRequest.FileStream.Close();
            }
        }

        private void OnGetResponse(IAsyncResult result)
        {
            try {
                // handle conversion result
                var uploadRequest = (UploadRequest)result.AsyncState;
                var response = (HttpWebResponse)uploadRequest.Request.EndGetResponse(result);

                Dispatcher.BeginInvoke(() => {
                    // assign loaded XPS stream to document data source
                    var stream = response.GetResponseStream();
                    this.DocumentDataSource.PackageReader = CreatePackageReader(stream);

                    UpdateState(false);
                });

            }
            catch (Exception e) {
                Dispatcher.BeginInvoke(() => {
                    UpdateState(false);
                    MessageBox.Show(e.ToString());
                });
            }
        }
    }
}
